namespace Plugins.Engine.Internal
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics.CodeAnalysis;
    using Plugins.Core;
    using Plugins.Core.Interfaces;
    using Plugins.Engine;
    using Plugins.Interfaces;
    using Plugins.Library;

    [Serializable]
    internal class PluginHost : PluginConfiguration, IPluginHostInternal, IPluginHost, IPluginCollectionContainer, IPluginEnumerator, IPluginActivatorInternal, IPluginHostContainer, IPluginActivator
    {
        private readonly Guid _id;// = Guid.NewGuid();
        private readonly Hashtable _contexts;
        private readonly List<IPluginObjectInternal> _plugins;

        private event EventHandler<PluginHostEventArgs> pluginLoad;
        private event EventHandler<PluginHostEventArgs> pluginUnload;

        public Guid Id { get { return _id; } }
        event EventHandler<PluginHostEventArgs> IPluginHostContainer.PluginLoad
        {
            add
            {
                if (!PluginHelper.GetInvocationList(this.pluginLoad, value))
                {
                    this.pluginLoad = (EventHandler<PluginHostEventArgs>) Delegate.Combine(this.pluginLoad, value);
                }
            }
            remove
            {
                if (PluginHelper.GetInvocationList(this.pluginLoad, value))
                {
                    this.pluginLoad = (EventHandler<PluginHostEventArgs>) Delegate.Remove(this.pluginLoad, value);
                }
            }
        }

        event EventHandler<PluginHostEventArgs> IPluginHostContainer.PluginUnload
        {
            add
            {
                if (!PluginHelper.GetInvocationList(this.pluginUnload, value))
                {
                    this.pluginUnload = (EventHandler<PluginHostEventArgs>) Delegate.Combine(this.pluginUnload, value);
                }
            }
            remove
            {
                if (PluginHelper.GetInvocationList(this.pluginUnload, value))
                {
                    this.pluginUnload = (EventHandler<PluginHostEventArgs>) Delegate.Remove(this.pluginUnload, value);
                }
            }
        }

        //public PluginHost(string hostName, string fileName, bool isDefault) : base(hostName, fileName, isDefault)
        //{
        //    this._contexts = new Hashtable();
        //    this._plugins = new List<IPluginObjectInternal>();
        //}

        public PluginHost(Guid value, string hostName, string fileName, bool isDefault) : base(hostName, fileName, isDefault)
        {
            this._id = value;
            this._contexts = new Hashtable();
            this._plugins = new List<IPluginObjectInternal>();
        }

        [SuppressMessage("Microsoft.Performance", "CA1811")]
        public IPluginObjectCollection<T> GetActivePlugins<T>()
        {
            IPluginCollectionContainer host = this;
            PluginObjectCollection<T> collection = new PluginObjectCollection<T>();
            IEnumerable<IPluginObject> objects = host.ActivePlugins;
            if (objects != null)
            {
                foreach (IPluginObject plugin in objects)
                {
                    IPluginObject<T> obj = plugin as IPluginObject<T>;
                    if (obj != null)
                    {
                        collection.Add(obj);
                    }
                }
            }
            return collection;
        }

        [SuppressMessage("Microsoft.Performance", "CA1811")]
        public IPluginObjectCollection<T> GetLoadedPlugins<T>()
        {
            IPluginCollectionContainer host = this;
            PluginObjectCollection<T> collection = new PluginObjectCollection<T>();
            IEnumerable<IPluginObject> objects = host.LoadedPlugins;
            if (objects != null)
            {
                foreach (IPluginObject plugin in objects)
                {
                    IPluginObject<T> obj = plugin as IPluginObject<T>;
                    if (obj != null)
                    {
                        collection.Add(obj);
                    }
                }
            }
            return collection;
        }

        private IEnumerable<IPluginObject> GetPlugins(Predicate<IPluginObjectInternal> match)
        {
            ICollection<IPluginObject> values = new Collection<IPluginObject>();
            foreach (IPluginObjectInternal plugin in this._plugins)
            {
                if (match(plugin))
                {
                    values.Add(plugin);
                }
            }
            return values;
        }

        private void Load(IPluginObjectInternal plugin)
        {
            if (this.pluginLoad != null)
            {
                this.pluginLoad(ConfigurationManager, new PluginHostEventArgs(this, plugin.InternalActivator.InternalContext.Configuration, plugin));
            }
        }

        private static IPluginObjectInternal Load<T>(IPluginHostInternal host, IPluginContextInternal context, Type assemblyType)
        {
            IPluginObjectInternal plugin = context.Load<T>(assemblyType);
            if (plugin != null)
            {
                host.RegisterPlugin(plugin);
            }
            return plugin;
        }

        private void Unload(IPluginObjectInternal plugin)
        {
            if (this.pluginUnload != null)
            {
                this.pluginUnload(ConfigurationManager, new PluginHostEventArgs(this, plugin.InternalActivator.InternalContext.Configuration, plugin));
            }
        }

        //private static void Unload(IPluginHostInternal host, IPluginObjectInternal plugin)
        //{
        //    IPluginContextInternal context = plugin.InternalActivator.InternalContext;
        //    host.UnregisterPlugin(plugin);
        //    context.Unload(plugin);
        //}

        private static IPluginObjectInternal Unload<T>(IPluginHostInternal host, IPluginContextInternal context, Type assemblyType)
        {
            IPluginObjectInternal plugin = context.Unload<T>(assemblyType);
            if (plugin != null)
            {
                host.UnregisterPlugin(plugin);
            }
            return plugin;
        }

        bool IPluginActivatorInternal.Activate(IPluginObjectInternal plugin)
        {
            return (((plugin != null) && (plugin.Host == this)) && plugin.Activate());
        }

        bool IPluginActivatorInternal.Deactivate(IPluginObjectInternal plugin)
        {
            return (((plugin != null) && (plugin.Host == this)) && plugin.Deactivate());
        }

        bool IPluginActivatorInternal.IsActive(IPluginObjectInternal plugin)
        {
            return ((plugin != null) && plugin.IsActive);
        }

        //void IPluginHostInternal.Clear()
        //{
        //    IPluginHostInternal _host = this;
        //    try
        //    {
        //        foreach (IPluginObjectInternal obj in _host.InternalPlugins)
        //        {
        //            _host.UnloadPlugin(obj);
        //        }
        //        foreach (IPluginContextInternal context in this._contexts.Values)
        //        {
        //            _host.UnloadContext(context.Configuration);
        //        }
        //    }
        //    catch (AppDomainUnloadedException ex)
        //    {
        //        throw new PluginDomainException(ex);
        //    }
        //}

        IPluginContextInternal IPluginHostInternal.this[IPluginAssemblyConfiguration pluginConfiguration]
        {
            get
            {
                return (this._contexts[pluginConfiguration] as IPluginContextInternal);
            }
        }

        IPluginObjectCollection<T> IPluginHostInternal.Load<T>(IPluginAssemblyConfiguration configuration)
        {
            IPluginObjectCollection<T> plugins = null;
            IPluginHostInternal _host = this;
            try
            {
                IPluginContextInternal context = _host[configuration];
                if (context == null)
                {
                    context = _host.LoadContext(configuration);
                    plugins = _host.LoadPlugin<T>(context);
                }
            }
            catch (AppDomainUnloadedException ex)
            {
                throw new PluginDomainException(ex);
            }
            return plugins;
        }

        [SuppressMessage("Microsoft.Design", "CA1031")]
        IPluginContextInternal IPluginHostInternal.LoadContext(IPluginAssemblyConfiguration pluginConfiguration)
        {
            try
            {
                if (pluginConfiguration != null)
                {
                    if (!string.IsNullOrEmpty(pluginConfiguration.Name))
                    {
                        if (this._contexts.Contains(pluginConfiguration))
                        {
                            return (this._contexts[pluginConfiguration] as IPluginContextInternal);
                        }
                        IPluginContextInternal context = new PluginContext(this, pluginConfiguration);
                        if (context.Load())
                        {
                            this._contexts.Add(pluginConfiguration, context);
                            return context;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                PluginLog.WriteErrorSync(ex);
            }
            return null;
        }

        [SuppressMessage("Microsoft.Design", "CA1031")]
        IPluginObjectCollection<T> IPluginHostInternal.LoadPlugin<T>(IPluginContextInternal context)
        {
            if (context != null)
            {
                PluginObjectCollection<T> objects = new PluginObjectCollection<T>();
                if (!context.IsLoaded)
                {
                    return objects;
                }
                Type[] types = context.Assembly.Types;
                if (types == null)
                {
                    return objects;
                }
                foreach (Type type in types)
                {
                    if (PluginTypeHelper.IsBaseType(typeof(T), type) && PluginTypeHelper.HasDefaultConstructor(type))
                    {
                        IPluginObject<T> obj = Load<T>(this, context, type) as IPluginObject<T>;
                        if (obj != null)
                        {
                            objects.Add(obj);
                        }
                    }
                }
                return objects;
            }
            return null;
        }

        void IPluginHostInternal.RegisterPlugin(IPluginObject plugin)
        {
            IPluginObjectInternal obj = plugin as IPluginObjectInternal;
            if ((obj != null) && !this._plugins.Contains(obj))
            {
                this._plugins.Add(obj);
                if (IsDefault)
                {
                    IPluginHost host = this;
                    host.Activate(obj);
                }
                this.Load(obj);
            }
        }

        //void IPluginHostInternal.Unload(IPluginObject plugin)
        //{
        //    if (plugin != null)
        //    {
        //        IPluginHostInternal _host = this;
        //        try
        //        {
        //            IPluginObjectInternal obj = (IPluginObjectInternal) plugin;
        //            _host.UnloadPlugin(obj);
        //        }
        //        catch (AppDomainUnloadedException ex)
        //        {
        //            throw new PluginDomainException(ex);
        //        }
        //    }
        //}

        IPluginObjectCollection<T> IPluginHostInternal.Unload<T>(IPluginAssemblyConfiguration configuration)
        {
            IPluginObjectCollection<T> plugins = null;
            IPluginHostInternal _host = this;
            try
            {
                IPluginContextInternal context = _host[configuration];
                if (context != null)
                {
                    plugins = _host.UnloadPlugin<T>(context);
                    context = _host.UnloadContext(configuration);
                }
            }
            catch (AppDomainUnloadedException ex)
            {
                throw new PluginDomainException(ex);
            }
            return plugins;
        }

        [SuppressMessage("Microsoft.Design", "CA1031")]
        IPluginContextInternal IPluginHostInternal.UnloadContext(IPluginAssemblyConfiguration pluginConfiguration)
        {
            try
            {
                if (pluginConfiguration != null)
                {
                    if (!string.IsNullOrEmpty(pluginConfiguration.Name))
                    {
                        if (!this._contexts.Contains(pluginConfiguration))
                        {
                            return null;
                        }
                        IPluginContextInternal context = this._contexts[pluginConfiguration] as IPluginContextInternal;
                        if (context.Unload())
                        {
                            this._contexts.Remove(context);
                            return context;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                PluginLog.WriteErrorSync(ex);
            }
            return null;
        }

        [SuppressMessage("Microsoft.Design", "CA1031")]
        IPluginObjectCollection<T> IPluginHostInternal.UnloadPlugin<T>(IPluginContextInternal context)
        {
            if (context != null)
            {
                PluginObjectCollection<T> objects = new PluginObjectCollection<T>();
                if (!context.IsLoaded)
                {
                    return objects;
                }
                Type[] types = context.Assembly.Types;
                if (types == null)
                {
                    return objects;
                }
                foreach (Type type in types)
                {
                    if (PluginTypeHelper.IsBaseType(typeof(T), type) && PluginTypeHelper.HasDefaultConstructor(type))
                    {
                        IPluginObject<T> obj = Unload<T>(this, context, type) as IPluginObject<T>;
                        if (obj != null)
                        {
                            objects.Add(obj);
                        }
                    }
                }
                return objects;
            }
            return null;
        }

        //void IPluginHostInternal.UnloadPlugin(IPluginObjectInternal plugin)
        //{
        //    if ((plugin != null) && plugin.InternalActivator.InternalContext.IsLoaded)
        //    {
        //        Unload(this, plugin);
        //    }
        //}

        void IPluginHostInternal.UnregisterPlugin(IPluginObject plugin)
        {
            IPluginObjectInternal obj = plugin as IPluginObjectInternal;
            if ((obj != null) && this._plugins.Contains(obj))
            {
                this.Unload(obj);
                if (IsDefault)
                {
                    IPluginHost host = this;
                    host.Deactivate(obj);
                }
                this._plugins.Remove(obj);
            }
        }

        bool IPluginActivator.Activate(IPluginObject plugin)
        {
            IPluginActivatorInternal host = this;
            IPluginObjectInternal obj = plugin as IPluginObjectInternal;
            return host.Activate(obj);
        }

        bool IPluginActivator.Deactivate(IPluginObject plugin)
        {
            IPluginActivatorInternal host = this;
            IPluginObjectInternal obj = plugin as IPluginObjectInternal;
            return host.Deactivate(obj);
        }

        bool IPluginActivator.IsActive(IPluginObject plugin)
        {
            IPluginActivatorInternal host = this;
            IPluginObjectInternal obj = plugin as IPluginObjectInternal;
            return host.IsActive(obj);
        }

        IEnumerable<IPluginObject> IPluginEnumerator.GetActivePlugins(Predicate<IPluginObject> match)
        {
            IPluginCollectionContainer host = this;
            IEnumerable<IPluginObject> objects = host.ActivePlugins;
            if (objects != null)
            {
                foreach (IPluginObject plugin in objects)
                {   
                    if (match(plugin))
                    {
                        yield return plugin;
                    }
                }
            }
        }

        IEnumerable<IPluginObject> IPluginEnumerator.GetLoadedPlugins(Predicate<IPluginObject> match)
        {
            IPluginCollectionContainer host = this;
            IEnumerable<IPluginObject> objects = host.LoadedPlugins;
            if (objects != null)
            {
                foreach (IPluginObject plugin in objects)
                {
                    if (match(plugin))
                    {
                        yield return plugin;
                    }
                }
            }
        }

        public int Count
        {
            get
            {
                return this._plugins.Count;
            }
        }

        //IEnumerable<IPluginObjectInternal> IPluginHostInternal.InternalPlugins
        //{
        //    get
        //    {
        //        ICollection<IPluginObjectInternal> values = new Collection<IPluginObjectInternal>();
        //        foreach (IPluginObjectInternal plugin in this._plugins)
        //        {
        //            if (plugin.InternalActivator.InternalContext.IsLoaded)
        //            {
        //                values.Add(plugin);
        //            }
        //        }
        //        return values;
        //    }
        //}

        IEnumerable<IPluginObject> IPluginCollectionContainer.ActivePlugins
        {
            get
            {
                return this.GetPlugins((IPluginObjectInternal obj) => obj.IsActive);
            }
        }

        IEnumerable<IPluginObject> IPluginCollectionContainer.LoadedPlugins
        {
            get
            {
                return this.GetPlugins((IPluginObjectInternal obj) => obj.InternalActivator.InternalContext.IsLoaded);
            }
        }
    }
}

